package br.unb.statistic.inference;

import java.io.File;
import java.io.IOException;

import org.jfree.data.Values;

import Jama.Matrix;

import br.unb.graphics.inference.InferenceChart;
import br.unb.statistic.SpectrumProcess;
import br.unb.statistic.inference.dataReader.DataReader;
import br.unb.statistic.distribution.GenericContinuousDistribution;
import br.unb.utils.LogFile;
import br.unb.utils.LogPrinter;

public class Inference {

    public static final String INTER_PACKET_TIMES = "interPacketTimes";
    public static final String PACKETS_PER_UNIT = "packetsPerTime";
    public static final String WHITTLE_ESTIMATOR = "Whittle";
    public static final String VARIANCE_TIME_PLOT_ESTIMATOR = "Variance-Time Plot";
    public static final String KETTANI_GUBNER_ESTIMATOR = "Kettani-Gubner";
    public static final String PERIODOGRAM_ESTIMATOR = "Periodogram";
    private int degreesOfFreedom = 30;
    private double p = (1.0d / (this.degreesOfFreedom + 1));
    private final int maxPoints = 40;
    private int column = 0;
    private double timeUnit = 1000000;
    private DataReader dr;
    private String process = "";

    public Inference(DataReader dr) {
        this.dr = dr;
    }

    public Inference(DataReader dr, int column) {
        this.dr = dr;
        this.column = column;
    }

    public Inference(DataReader dr, double timeUnit) {
        this.dr = dr;
        this.timeUnit = timeUnit;
    }

    public Inference(DataReader dr, int column, double timeUnit) {
        this.dr = dr;
        this.column = column;
        this.timeUnit = timeUnit;
    }

    public Inference(DataReader dr, String process) {
        this.dr = dr;
        this.process = process;
    }

    public Inference(DataReader dr, int column, String process) {
        this.dr = dr;
        this.column = column;
        this.process = process;
    }

    public Inference(DataReader dr, double timeUnit, String process) {
        this.dr = dr;
        this.timeUnit = timeUnit;
        this.process = process;
    }

    public Inference(DataReader dr, int column, double timeUnit, String process) {
        this.dr = dr;
        this.column = column;
        this.timeUnit = timeUnit;
        this.process = process;
    }

    public String nextValue() throws IOException {
        if (process.equals(Inference.PACKETS_PER_UNIT)) {
            return dr.nextPacketsPerTimeValueAt(column, timeUnit);
        } else if (process.equals(Inference.INTER_PACKET_TIMES)) {
            return dr.nextInterPacketValueAt(column);
        } else {
            return dr.nextValueAt(column);
        }
    }

    private void getIntervals(double[] intervals, GenericContinuousDistribution distribution) {
        int i;
        for (i = 0; i < this.degreesOfFreedom; i++) {
            intervals[i] = distribution.inverseCdf(this.p * (i + 1));
        }
    }

    public long getSampleSize() {
        long n = 0;
        try {
            rewind();
            String valor = null;
            while ((valor = this.nextValue()) != null) {
                try {
                    java.lang.Double.parseDouble(valor);
                    n++;
                } catch (NumberFormatException nfe) {
                }
            }
            dr.close();
        } catch (IOException ioe) {
        }
        return n;
    }

    public double getMean() {
        double media = 0;
        long n = 0;
        try {
            rewind();
            String valor = null;
            while ((valor = this.nextValue()) != null) {
                try {
                    media += java.lang.Double.parseDouble(valor);
                    n++;
                } catch (NumberFormatException nfe) {
                }
            }
            if (n != 0) {
                media /= n;
            }
            dr.close();
        } catch (IOException ioe) {
        }
        return media;
    }

    public double getMeanSquare() {
        double media2 = 0;
        long n = 0;
        try {
            rewind();
            String valor = null;
            while ((valor = this.nextValue()) != null) {
                try {
                    media2 += Math.pow(java.lang.Double.parseDouble(valor), 2);
                    n++;
                } catch (NumberFormatException nfe) {
                }
            }
            if (n != 0) {
                media2 /= n;
            }
            dr.close();
        } catch (IOException ioe) {
        }
        return media2;
    }

    public double getVariance() {
        double media = this.getMean();
        double variance = 0;
        long n = 0;
        try {
            rewind();
            String valor = null;
            while ((valor = this.nextValue()) != null) {
                try {
                    variance += Math.pow((java.lang.Double.parseDouble(valor) - media), 2);
                    n++;
                } catch (NumberFormatException nfe) {
                }
            }
            variance /= (n - 1);
            dr.close();
        } catch (IOException ioe) {
        }
        return variance;
    }

    public double getHurst(String estimator) {
        HEstimator h;
        if (estimator.equals(Inference.KETTANI_GUBNER_ESTIMATOR)) {
            h = new KettaniGubnerEstimator(this);
        } else if (estimator.equals(Inference.VARIANCE_TIME_PLOT_ESTIMATOR)) {
            h = new VarianceTimePlotEstimator(this);
        } else if (estimator.equals(Inference.WHITTLE_ESTIMATOR)) {
            h = new WhittleEstimator(this);
        } else if (estimator.equals(Inference.PERIODOGRAM_ESTIMATOR)) {
            h = new PeriodogramEstimator(this);
        } else {
            return 0;
        }
        return h.getHurst();
    }

    public double getAutocovariance(int lag) {
        double media = this.getMean();
        double aux[] = new double[lag];
        double autocovariance = 0, x;

        int index = 0;
        long n = 0;
        try {
            rewind();
            String valor = null;
            while (index < lag && (valor = this.nextValue()) != null) {
                try {
                    aux[index] = java.lang.Double.parseDouble(valor);
                    n++;
                    index++;
                } catch (NumberFormatException nfe) {
                }
            }
            index = 0;
            while ((valor = this.nextValue()) != null) {
                try {
                    x = java.lang.Double.parseDouble(valor);
                    autocovariance += ((aux[index] - media) * (x - media));
                    aux[index] = x;
                    index++;
                    if (index == lag) {
                        index = 0;
                    }
                    n++;
                } catch (NumberFormatException nfe) {
                }
            }
            autocovariance /= n;
            dr.close();
        } catch (IOException ioe) {
        }
        return autocovariance;
    }

    public double getAutocorrelation(int lag) {
        return (this.getAutocovariance(lag) / this.getVariance());

    }

    public double getQui(GenericContinuousDistribution distribution) {
        long obtido[] = new long[this.degreesOfFreedom + 1];
        double intervals[] = new double[this.degreesOfFreedom + 1];
        this.getIntervals(intervals, distribution);
        String valor = null;
        double amostra;
        double qui = 0;
        try {
            rewind();
            int i;
            for (i = 0; (i < this.degreesOfFreedom + 1); i++) {
                obtido[i] = 0;
            }
            long n = 0;
            while ((valor = this.nextValue()) != null) {
                try {
                    amostra = java.lang.Double.parseDouble(valor);
                    n++;
                    for (i = 0; (amostra > intervals[i]) && (i < this.degreesOfFreedom); i++);
                    (obtido[i])++;
                } catch (NumberFormatException nfe) {
                }
            }
            double aux = 0;

            for (i = 0; i <= this.degreesOfFreedom; i++) {

                aux = (Math.pow((n * this.p - (double) obtido[i]), 2)) / (n * this.p);
                qui = qui + aux;
            }
            dr.close();
        } catch (IOException ioe) {
        }
        return (qui);
    }

    public int getDegreesOfFreedom() {
        return degreesOfFreedom;
    }

    public void setDegreesOfFreedom(int degreesOfFreedom) {
        this.degreesOfFreedom = degreesOfFreedom;
        p = (1.0d / (this.degreesOfFreedom + 1));
    }

    public double getMax() {
        double max = java.lang.Double.MIN_VALUE, aux;
        try {
            rewind();
            String nextString = "";
            while ((nextString = this.nextValue()) != null) {
                try {
                    if ((aux = java.lang.Double.parseDouble(nextString)) > max) {
                        max = aux;
                    }
                } catch (NumberFormatException nfe) {
                }

            }
            dr.close();
        } catch (IOException ioe) {
        }
        return max;
    }

    public double getMin() {
        double min = java.lang.Double.MAX_VALUE, aux;
        try {
            rewind();
            String nextString = "";
            while ((nextString = this.nextValue()) != null) {
                try {
                    if ((aux = java.lang.Double.parseDouble(nextString)) < min) {
                        min = aux;
                    }
                } catch (NumberFormatException nfe) {
                }

            }
            dr.close();
        } catch (IOException ioe) {
        }
        return min;
    }

    public static double slope(double[] x, double[] y) {
        double somaX = 0, somaY = 0, somaXY = 0, somaXX = 0;
        int length = y.length;
        if (length > x.length) {
            length = x.length;
        }
        for (int i = 0; i < length; i++) {
            somaX += x[i];
            somaY += y[i];
            somaXX += x[i] * x[i];
            somaXY += x[i] * y[i];
        }

        return (length * somaXY - somaX * somaY) / (length * somaXX - somaX * somaX);
    }

    public static double[] getBestFitPolynomialCoefficients(double x[], double y[], int degree) {
        int length = y.length;
        double alfa[] = new double[degree + 1];
        double a[][] = new double[length][degree + 1];
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < degree + 1; j++) {
                a[i][j] = Math.pow(x[i], j);
            }
        }
        Matrix A = new Matrix(a);
        Matrix Y = new Matrix(y, length);
        Matrix Alfa = A.solve(Y);
        alfa = Alfa.getColumnPackedCopy();
        return alfa;
    }

    public static double[][] getBestFitPolynomial(double x[], double y[], int degree) {
        int length = y.length;
        double result[][] = new double[2][length];
        double yy[] = new double[length];
        double a[][] = new double[length][degree + 1];
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < degree + 1; j++) {
                a[i][j] = Math.pow(x[i], j);
            }
        }
        Matrix A = new Matrix(a);
        Matrix Y = new Matrix(y, length);
        Matrix Alfa = A.solve(Y);
        Matrix Result = A.times(Alfa);
        yy = Result.getColumnPackedCopy();
        for (int i = 0; i < length; i++) {
            result[0][i] = x[i];
            result[1][i] = yy[i];
        }
        return result;
    }

    public static double[] removeTrend(double x[], double y[], int degree) {
        int length = y.length;
        double result[] = new double[length];
        double a[][] = new double[length][degree + 1];
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < degree + 1; j++) {
                a[i][j] = Math.pow(x[i], j);
            }
        }
        Matrix A = new Matrix(a);
        Matrix Y = new Matrix(y, length);
        Matrix Alfa = A.solve(Y);
        Matrix aux = A.times(Alfa);
        Matrix Result = Y.minus(aux);
        result = Result.getColumnPackedCopy();
        return result;
    }

    public double getAggregatedVar(int m) {

        int n = 0;
        LogFile auxFile = new LogFile(LogFile.Type.DUMP, "AuxHurst" + m);
        LogPrinter auxPrinter = new LogPrinter(auxFile);
        double var = 0;
        try {
            this.rewind();
            String valor = null;
            double soma = 0;
            valor = this.nextValue();
            while (valor != null) {
                for (n = 0; n < m && (valor != null); n++) {
                    try {
                        soma += java.lang.Double.parseDouble(valor);
                    } catch (NumberFormatException nfe) {
                    }
                    valor = this.nextValue();
                }

                auxPrinter.write((soma / n) + "");
                soma = 0;
            }
            auxPrinter.close();
            this.getDataReader().close();
            Inference inf = new Inference(new DataReader(auxFile));
            var = inf.getVariance();
        } catch (IOException ioe) {
        }

        //TODO verify this delete below
        auxFile.delete();

        return var;
    }

    public double[] getBestFitPolynomialCoefficients(int degree) {
        double result[] = new double[degree + 1];
        int n = (int) getSampleSize();
        double[] x = new double[n];
        double y[] = new double[n];
        try {
            this.rewind();
            for (int i = 0; i < n; i++) {
                x[i] = i + 1;
            }
            for (int i = 0; i < n; i++) {
                y[i] = java.lang.Double.parseDouble(this.nextValue());
            }
            result = Inference.getBestFitPolynomialCoefficients(x, y, degree);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    public double[][] getBestFitPolynomial(int degree) {
        int n = (int) getSampleSize();
        double result[][] = new double[2][n];
        double[] x = new double[n];
        double y[] = new double[n];
        try {
            this.rewind();
            for (int i = 0; i < n; i++) {
                x[i] = i + 1;
            }
            for (int i = 0; i < n; i++) {
                y[i] = java.lang.Double.parseDouble(this.nextValue());
            }
            result = Inference.getBestFitPolynomial(x, y, degree);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    public void removeTrend(int degree) {
        double result[] = new double[degree + 1];
        int n = (int) getSampleSize();
        double[] x = new double[n];
        double y[] = new double[n];
        try {
            this.rewind();
            for (int i = 0; i < n; i++) {
                x[i] = i + 1;
            }
            for (int i = 0; i < n; i++) {
                y[i] = java.lang.Double.parseDouble(this.nextValue());
            }
            result = Inference.removeTrend(x, y, degree);
            LogFile logFile = new LogFile(LogFile.Type.DUMP, "Residual_" + degree + "degrees-" + dr.getFile().getName());
            LogPrinter logPrinter = new LogPrinter(logFile);
            for (int i = 0; i < result.length; i++) {
                logPrinter.write( Double.toString(result[i]));
            }
            logPrinter.close();
            File file = logFile;
            dr.close();
            dr = new DataReader(file);
            column = 0;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void loadDistributionChart(InferenceChart chart, String seriesName) {
        double valor, aux;
        int points;
        double min = this.getMin();
        double max = this.getMax();
        points = maxPoints;
        aux = (max - min) / points;

        long distribuicao[] = new long[points + 1];
        try {
            rewind();
            String nextString = "";

            chart.addTraffic(seriesName);
            long n = 0;
            while ((nextString = this.nextValue()) != null) {
                try {
                    valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    int i;
                    for (i = 0; aux * (i + 1) <= (valor - min); i++);
                    (distribuicao[i])++;
                } catch (NumberFormatException nfe) {
                }
            }
            /*	int i=1;
            if(isDiscrete){
            i = 0;
            }*/
            for (int i = 0; i <= points; i++) {
                chart.addValueToTraffic(((double) distribuicao[i] / n) * 100.0d, (double) (min + aux * (i + 0.5d)), seriesName);
            }
            chart.setWidth(aux);
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadCDFChart(InferenceChart chart, String seriesName) {
        double valor, aux;
        int points;
        double min = this.getMin();
        double max = this.getMax();
        points = maxPoints;
        aux = (max - min) / points;

        long distribuicao[] = new long[points + 1];
        try {
            rewind();
            String nextString = "";

            chart.addTraffic(seriesName);
            long n = 0;
            while ((nextString = this.nextValue()) != null) {
                try {
                    valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    int i;
                    for (i = points; aux * (i + 1) > (valor - min); i--) {
                        (distribuicao[i])++;
                    }
                } catch (NumberFormatException nfe) {
                }
            }
            for (int i = 0; i <= points; i++) {
                chart.addValueToTraffic(((double) distribuicao[i] / n) * 100.0d, (double) (min + aux * (i + 1)), seriesName);
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadTailDistributionChart(InferenceChart chart, String seriesName) {
        double valor, aux;
        int points;
        double min = this.getMin();
        double max = this.getMax();
        points = maxPoints;
        aux = (max - min) / points;

        long distribuicao[] = new long[points + 1];
        try {
            rewind();
            String nextString = "";

            chart.addTraffic(seriesName);
            long n = 0;
            while ((nextString = this.nextValue()) != null) {
                try {
                    valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    int i;
                    for (i = 0; aux * (i + 1) < (valor - min); i++) {
                        (distribuicao[i])++;
                    }
                } catch (NumberFormatException nfe) {
                }
            }
            for (int i = 0; i <= points; i++) {
                chart.addValueToTraffic(((double) distribuicao[i] / n) * 100.0d, (double) (min + aux * (i + 1)), seriesName);
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadSamplePathChart(InferenceChart chart, String seriesName) {
        try {
            rewind();
            String nextString = "";
            long n = 0;
            chart.addTraffic(seriesName);
            while ((nextString = this.nextValue()) != null) {
                try {
                    double valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    chart.addValueToTraffic(valor, n, seriesName);
                } catch (NumberFormatException nfe) {
                }
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadVTPChart(InferenceChart chart, String seriesName) {
        try {
            rewind();
            chart.addTraffic(seriesName);
            final int len = 50;
            for (int i = 0; i < len; i++) {
                chart.addValueToTraffic(getAggregatedVar(i + 1), i + 1, seriesName);
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadTrendChart(InferenceChart chart, String seriesName, int degree) {
        try {
            rewind();
            chart.addTraffic(seriesName);
            double values[][] = this.getBestFitPolynomial(degree);
            System.out.println(values[0].length);
            for (int i = 0; i < values[0].length; i++) {
                chart.addValueToTraffic(values[1][i], values[0][i], seriesName);
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public void loadLogVTPChart(InferenceChart chart, String seriesName) {
        try {
            rewind();
            chart.addTraffic(seriesName);
            final int len = 50;
            for (int i = 0; i < len; i++) {
                chart.addValueToTraffic(Math.log(getAggregatedVar(i + 1)), Math.log(i + 1), seriesName);
            }
            dr.close();

        } catch (IOException ioe) {
        }
        Runtime.getRuntime().gc();
    }

    public double getPrLowerEqualThan(double x) {
        double p = 0;
        try {
            rewind();
            String nextString = "";
            long n = 0;
            double valor, aux = 0;
            while ((nextString = this.nextValue()) != null) {
                try {
                    valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    if (valor <= x) {
                        aux++;
                    }
                } catch (NumberFormatException nfe) {
                }
            }
            dr.close();
            p = aux / n;
        } catch (IOException ioe) {
        }
        return p;
    }

    public double getPrGreaterThan(double x) {
        double p = 0;
        try {
            rewind();
            String nextString = "";
            long n = 0;
            double valor, aux = 0;
            while ((nextString = this.nextValue()) != null) {
                try {
                    valor = java.lang.Double.parseDouble(nextString);
                    n++;
                    if (valor > x) {
                        aux++;
                    }
                } catch (NumberFormatException nfe) {
                }
            }
            dr.close();
            p = aux / n;
        } catch (IOException ioe) {
        }
        return p;
    }

    public int getmaxPoints() {
        return maxPoints;
    }

    public DataReader getDataReader() {
        return dr;
    }

    public double getTimeUnit() {
        return timeUnit;
    }

    public void setTimeUnit(double timeUnit) {
        this.timeUnit = timeUnit;
    }

    public int getColumn() {
        return column;
    }

    public void setColumn(int column) {
        this.column = column;
    }

    public void rewind() throws IOException {
        File file = dr.getFile();
        String comment = dr.getCommentary();
        String delimiter = dr.getDelimiter();
        dr.close();
        dr = new DataReader(delimiter, file, comment);
    }

    public String getProcess() {
        return process;
    }

    public void setProcess(String process) {
        this.process = process;
    }
    /* //Old code with the testing unit and stuff YO!!!
    public static void main(String args[]){
    Inference inf;
    try {
    inf = new Inference(new DataReader(new LogFile("C:\\eclipse\\workspace\\Analisador\\LogFile\\total.dt")));
    double r[][] = inf.getBestFitPolynomial(10);
    LogFile log = new LogFile("testese",false);
    for(int i=0;i<inf.getSampleSize();i++){
    log.write(""+r[0][i]+","+r[1][i]);
    }
    log.close();
    inf.removeTrend(10);
    } catch (IOException e) {
    e.printStackTrace();
    }

    }*/
}
